/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "WSAImpl.h"
#include <logs/Log.h>


namespace liba
{
namespace net
{

#pragma comment(lib,"wsock32.lib")
#pragma comment(lib,"ws2_32.lib")
//----------------------------------------------------------
// WSAImpl
//----------------------------------------------------------
int WSAImpl::n_created = 0;
bool WSAImpl::wsa_valid = false;

WSAImpl::WSAImpl()
:   socket(INVALID_SOCKET),
	event(WSA_INVALID_EVENT) 
{
    if(++n_created == 1)
	    StartWSA();

	if(SystemValid())
		event = WSACreateEvent();
}

WSAImpl::~WSAImpl()
{
	CloseEvent();
    Close();
    if( --n_created == 0 )
	    StopWSA();
}

void WSAImpl::StartWSA(void)
{
    wsa_valid = false;

    WORD wVersionRequested;
	WSADATA wsaData;

    wVersionRequested = MAKEWORD( 2, 0 );

    int err = WSAStartup( wVersionRequested, &wsaData );
	if( err ) 
	{
		logs::log() << logs::error << "WSA: Unable to start WSA service.\n";
	    return;
	}

    if( LOBYTE( wsaData.wVersion ) != 2 ||
	    HIBYTE( wsaData.wVersion ) != 0 ) 
	{
	    WSACleanup( );
		logs::log() << logs::error << "WSA: Bad version WSA.\n";
	    return; 
	}
	wsa_valid = true;
}

void WSAImpl::StopWSA(void)
{
    if( wsa_valid )
	    WSACleanup();
	wsa_valid = false;
}

bool WSAImpl::EventSelect(long events)
{
    if(!wsa_valid)
	    return false;

	if(socket != INVALID_SOCKET && event != WSA_INVALID_EVENT)
		if(WSAEventSelect(socket, event, events) != SOCKET_ERROR)
			return true;
	return false;		
}

long WSAImpl::WaitEvents()
{
    if(!wsa_valid)
	    return 0;

	unsigned long  wait;
	if(event == WSA_INVALID_EVENT || socket == INVALID_SOCKET)
		return 0;

	if((wait = WSAWaitForMultipleEvents(1, &event, FALSE, 100, FALSE)) != WSA_WAIT_FAILED)
	{
		if(wait == WSA_WAIT_TIMEOUT)
			return 0;

		WSANETWORKEVENTS info_events;

		if(WSAEnumNetworkEvents(socket, event, &info_events) != SOCKET_ERROR)
		{
			long result = 0;
			
			if((info_events.lNetworkEvents & FD_ACCEPT) &&
			(info_events.iErrorCode[FD_ACCEPT_BIT] == 0))
				result |= FD_ACCEPT;
			if((info_events.lNetworkEvents & FD_CLOSE)/* &&
			(info_events.iErrorCode[FD_CLOSE_BIT] == 0)*/)
				result |= FD_CLOSE;
			if((info_events.lNetworkEvents & FD_READ) &&
			(info_events.iErrorCode[FD_READ_BIT] == 0))
				result |= FD_READ;
			if((info_events.lNetworkEvents & FD_WRITE) &&
			(info_events.iErrorCode[FD_WRITE_BIT] == 0))
				result |= FD_WRITE;
			if((info_events.lNetworkEvents & FD_CONNECT) &&
			(info_events.iErrorCode[FD_CONNECT_BIT] == 0))
				result |= FD_CONNECT;

			return result;
		}
	}
	return 0;
}

bool WSAImpl::CloseEvent()
{
	if(!wsa_valid)
		return false;
	
	if(event != WSA_INVALID_EVENT)
	{
		EventSelect(0);
		if(socket != INVALID_SOCKET)
			return (WSACloseEvent(event) == TRUE) ? true: false;
	}
	return false;
}

int WSAImpl::SetIntOption(int level, int optname, int val) const
{
	return ::setsockopt(socket, level, optname, (const char *)&val, sizeof(val));
}

int WSAImpl::SetOption(int level, int optname, const char * val_ptr, int size) const
{
	return ::setsockopt(socket, level, optname, val_ptr,  size);
}

void WSAImpl::Close()
{
	if(!wsa_valid)
		return;
	if(socket != INVALID_SOCKET)
	{
		::closesocket( socket );
		socket = INVALID_SOCKET;
	}
}
bool WSAImpl::is_valid()const
{
	return socket != INVALID_SOCKET;
}
bool WSAImpl::SystemValid()const
{
	return wsa_valid;
}

} // namespace net
} // namespace liba

